package com.xuyuan.ai.chat;

import com.xuyuan.ai.entity.ActorFilms;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author xuyuan
 * @desc 初识Spring AI ChatClient。参考SpringAI官方文档
 */
@RestController
public class ChatController {

    @Autowired
    @Qualifier("chatBotChatClient")
    private ChatClient chatBotChatClient;

    @Autowired
    @Qualifier("voiceChatBotChatClient")
    private ChatClient voiceChatBotChatClient;

    private final ChatClient chatClient;

    /**
     * 可以ChatClient.Builder通过设置属性来禁用自动配置spring.ai.chat.client.enabled=false。如果多个聊天模型一起使用，这很有用。以编程方式为每个需要创建一个ChatClient.Builder实例：ChatModel
     * ChatModel myChatModel = ... // usually autowired
     * 本案例使用的是spring-ai-openai-spring-boot-starter集成的是OpenAiChatModel
     * <p>
     * 如果不需要使用其他的模型可以参考下面的方式创建ChatClient
     * ChatClient.builder(ChatModel chatModel);
     * or
     * ChatClient.create(ChatModel chatModel);
     */
    public ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    /**
     * AI 模型的响应是一种由类型定义的丰富结构ChatResponse。它包含有关如何生成响应的元数据，还可以包含多个响应（称为 Generations），每个响应都有自己的元数据。
     * 元数据包括用于创建响应的tokens（每个token大约为一个单词的 3/4）。此信息很重要，因为 AI 模型根据每个请求使用的tokens收费。
     * <p>
     * call() 直接返回。（可以返回String||ChatResponse||entity）
     * stream（） 流式返回。（可以返回Flux<String>||Flux<ChatResponse>）
     */
    @GetMapping("/ai-1")
    String generation1(@RequestParam(value = "message", defaultValue = "hello") String userInput) {
        // 流式语法
        return chatClient.prompt().user(userInput).call().content();
    }

    /**
     * 对于模型返回的复杂对象，可以使用实体类进行解析。
     * <p>
     * CallResponseSpec返回定义了解析的方式。
     *
     * <T> T entity(Class<T> type) 返回指定对象
     * <T> T entity(ParameterizedTypeReference<T> type) 返回指定泛型的对象
     * <T> T entity(StructuredOutputConverter<T> structuredOutputConverter); 自定义标准输出转换器。参考generation4()
     */
    @GetMapping("/ai-2")
    ActorFilms generation2(@RequestParam(value = "message", defaultValue = "hello") String userInput) {
        // 大模型的响应不一定适配ActorFilms的结构。可以通过后续的转换器定义标准的Json结构提示语给大模型进行解析。参考#generation4()
        return chatClient.prompt().user("Generate the filmography for a random actor.").call().entity(ActorFilms.class);
    }

    /**
     * 流式响应
     */
    @GetMapping("/ai-3")
    Flux<String> generation3(@RequestParam(value = "message", defaultValue = "hello") String userInput) {
        return chatClient.prompt().user(userInput).stream().content();
    }

    /**
     * 自定义标准输出转换器。将响应的Json结构解析为ActorFilms对象。（预先定义好jsonSchema，让大模型返回指定json结构的响应）
     */
    @GetMapping("/ai-4")
    List<ActorFilms> generation4() {
        var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorFilms>>() {
        });
        var flux = chatClient.prompt().user(u -> u.text("""
                          Generate the filmography for a random actor.
                          {format}
                        """).param("format", converter.getFormat())) // 通过提示模板告诉大模型返回的格式
                .stream().content();
        var content = flux.collectList().block().stream().collect(Collectors.joining());
        return converter.convert(content);
        // 示例响应：
        // [{"actor":"John Doe","movies":["The Last Sunset","Echoes of Tomorrow","Shadows in the Mist","The Forgotten Path","Beneath the Crimson Sky"]}]
    }

    /**
     * chatBotChatClient - 默认的ChatClient系统，不需要在运行时额外传入Prompt
     */
    @GetMapping("/ai/simple")
    public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
        return Map.of("completion", chatBotChatClient.prompt().user(message).call().content());
    }

    /**
     *
     */
    @GetMapping("/ai-5")
    Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {
        return Map.of("completion", voiceChatBotChatClient.prompt().system(sp -> sp.param("voice", voice)).user(message).call().content());
    }


}